﻿// Copyright (c) Microsoft Corporation.  All rights reserved. 
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Collections.Generic;
using WiE.Model;
using Microsoft.Live.ServerControls.VE;
using System.Web.Services;

namespace WiEWebViewer
{
    public partial class _Default : System.Web.UI.Page
    {
        const int MAX_TIME_BETWEEN_LOCATIONS = 5; // break history trail if 5 minutes between points.

        IWiEModel m_model = null;

        protected void Page_Load(object sender, EventArgs e)
        {
            // Create instance of the data model for the application
            WiERemoteModelSSDS ssds = new WiERemoteModelSSDS();
            ssds.Initialize();

            // Set it as the model for the application
            m_model = ssds;

            // Set up the handler for the [Refresh] button.
            m_btnRefresh.Click += new EventHandler(m_btnRefresh_Click);

            // Is this the first time we're drawing the page?
            if (!IsPostBack)
            {
                // Build the list of friends
                PopulateFriendsList();

                // Build the list of points of interest
                PopulatePointsOfInterestList();
            }
        }

        /// <summary>
        /// Event handler for Refresh button, this method will retrieve the current / most recent locations for 
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void m_btnRefresh_Click(object sender, EventArgs e)
        {
            // Clear the map
            m_mainMap.Clear();

            // Clear out the status of the refresh
            m_labelRefreshStatus.Text = "";

            // Add all the selected friends to the map.
            MapSelectedFriends();

            // Add all the points of interests to the map
            MapSelectedPointsOfInterest();
        }

        /// <summary>
        /// Adds all selected friends to the map.
        /// </summary>
        protected void MapSelectedFriends()
        {
            // For each selected friend, attempt to retrieve the current location and add it as a point on the map
            foreach (ListItem item in m_checkBoxListFriends.Items)
            {
                // Was the item selected / check marked?
                if (item.Selected)
                {
                    // Add the friend to the map
                    AddFriendToMap(new System.Guid(item.Value));
                }
            }
        }

        /// <summary>
        /// Adds all the selected points of interest to the map.
        /// </summary>
        protected void MapSelectedPointsOfInterest()
        {
            foreach (ListItem item in m_checkBoxListPointsOfInterest.Items)
            {
                if (item.Selected)
                {
                    AddPointOfInterestToMap(new System.Guid(item.Value));
                }
            }
        }

        /// <summary>
        /// Adds shape (pin or trail) for a member to the map.
        /// </summary>
        /// <param name="p_guidMemberID"></param>
        protected void AddFriendToMap(System.Guid p_guidMemberID)
        {
            WiEMemberDataObject member = null;
            string strSelectedTimePeriod = m_radioButtonListSelectTimePeriod.Items[0].Value;

            // Retrieve the metadata about the member so we can show his or her name
            // and contact information
            member = m_model.GetMember(p_guidMemberID);
            
            // Retrieve the selected time period
            ListItem selectedTimePeriod = m_radioButtonListSelectTimePeriod.SelectedItem;
            if (selectedTimePeriod != null)
            {
                strSelectedTimePeriod = selectedTimePeriod.Value;
            }

            if (strSelectedTimePeriod == "_mostRecent")
            {
                // Show only the most recent position / location
                WiELocationDataObject mostRecentLocation = m_model.GetCurrentLocationForMember(member.MemberID);
                if (mostRecentLocation != null)
                {
                    double lat = (mostRecentLocation.Latitude != null) ? (double)mostRecentLocation.Latitude : 0;
                    double lon = (mostRecentLocation.Longitude != null) ? (double)mostRecentLocation.Longitude : 0;

                    // string strDescription = BuildDescription(p_member, mostRecentLocation);
                    Shape currentLocationShape = new Shape(ShapeType.Pushpin, new LatLongWithAltitude(lat, lon));
                    currentLocationShape.Description = BuildMemberLocationDescription(member, mostRecentLocation);

                    m_mainMap.AddShape(currentLocationShape);

                    m_mainMap.Center = new LatLong(lat, lon);

                    m_mainMap.ZoomLevel = 10;
                }
                else
                {
                    m_labelRefreshStatus.Text += "There was no current or recent GPS data for " + member.FirstName + ". ";
                }
            }
            else
            {
                // Show history trail of the last 15 minutes or last 24 hours
                int nMinutes = (strSelectedTimePeriod == "_lastFifteenMinutes") ? -15 : (-1 * 24 * 60);

                // Query the model for the location history in the specified time period (in UTC)
                List<WiELocationDataObject> locationHistory = m_model.GetLocationHistoryForMemberBetween(member.MemberID, DateTime.UtcNow.AddMinutes(nMinutes), DateTime.UtcNow);

                // If there was any history, plot the history as line segment(s).
                if ((locationHistory != null) && (locationHistory.Count > 0))
                {
                    // Now we attempt to split the history into "segments" of time, looking
                    // for any significant gaps between locations that could indicate the member
                    // had turned off tracking or was stopped.
                    List<List<WiELocationDataObject>> locationHistorySegments = SplitLocationHistory(locationHistory);

                    // We keep a list of all the points added to the map so we can ask the map
                    // to set its view to encompass all the points.
                    List<LatLongWithAltitude> pointsForMapView = new List<LatLongWithAltitude>(); ;

                    // We now want to draw a line segment for each "segment" of the history
                    foreach (List<WiELocationDataObject> locationHistorySegment in locationHistorySegments)
                    {
                        // Create a Virtual Earth polyline shape to represent / plot the locations
                        // for that time period segment.   Shapes are specified using a list of
                        // VE LatLongWithAltitude objects, so we convert from our location objects
                        // to VE LatLong objects.
                        List<LatLongWithAltitude> pointsForPolyline = new List<LatLongWithAltitude>();
                        foreach (WiELocationDataObject location in locationHistorySegment)
                        {
                            pointsForPolyline.Add(new LatLongWithAltitude((double)location.Latitude, (double)location.Longitude));
                        }

                        if (pointsForPolyline.Count > 1)
                        {
                            // Now create the polyline shape with the points and show pin Icon at the
                            // begining (latest point in time) of the line with summary information about
                            // that segment of time.
                            Shape historySegmentShape = new Shape(ShapeType.Polyline, pointsForPolyline);
                            historySegmentShape.IconVisible = true;
                            historySegmentShape.IconAnchor = pointsForPolyline[0];
                            historySegmentShape.Description = BuildMemberLocationHistoryDescription(member, locationHistorySegment);

                            // Add the shape to the map.
                            m_mainMap.AddShape(historySegmentShape);
                        }
                        else
                        {
                            // Special case, there was only one point in the time period, so we use a Pin rather
                            // than a polyline.
                            Shape pointShape = new Shape(ShapeType.Pushpin, pointsForPolyline[0]);
                            pointShape.Description = BuildMemberLocationDescription(member, locationHistorySegment[0]);
                            m_mainMap.AddShape(pointShape);
                        }

                        // Keep track of all the points we've added to the map so we can ask
                        // the map to set a view that encompasses all the points.
                        pointsForMapView.AddRange(pointsForPolyline);
                    }

                    // Ask the map to set a view that encompasses all the data points.
                    m_mainMap.SetMapView(pointsForMapView);
                }
                else
                {
                    m_labelRefreshStatus.Text += "There was no GPS data for the selected period for " + member.FirstName;
                }
            }            
        }

        /// <summary>
        /// Method that adds a point of interest to the map along with the relevant description
        /// </summary>
        /// <param name="p_guidPointOfInterest"></param>
        protected void AddPointOfInterestToMap(System.Guid p_guidPointOfInterest)
        {
        }

        /// <summary>
        /// Helper method that takes a list representing location history and splits it into
        /// smaller lists that represent distinct segments of the history, to capture times when
        /// the member may have switched off tracking or may have stopped for an extended period
        /// of time.
        /// </summary>
        /// <param name="p_locationHistory"></param>
        /// <returns></returns>
        private List<List<WiELocationDataObject>> SplitLocationHistory(List<WiELocationDataObject> p_locationHistory)
        {
            List<List<WiELocationDataObject>> locationHistorySegments = new List<List<WiELocationDataObject>>();

            // Go through the location history and anytime there appears to be a gap of at least
            // 5 minutes between readings, treat it as a new reading.   The mobile client has a 
            // small governor that attempts to minimize data collected but the 5 minute rule is
            // well outside the client's governor rules.
            WiELocationDataObject prevLocation = null;
            List<WiELocationDataObject> currentSegment = new List<WiELocationDataObject>();
            foreach (WiELocationDataObject location in p_locationHistory)
            {
                if (prevLocation != null)
                {
                    // Check to see if there was a significant gap in time or distance between
                    // these points.
                    double minutesEllapsed = Math.Abs(((DateTime)location.DateCollected).Subtract((DateTime)prevLocation.DateCollected).TotalMinutes);
                    double distance = location.DistanceTo(prevLocation);
                    if (minutesEllapsed >= MAX_TIME_BETWEEN_LOCATIONS)
                    {
                        // There was a significant amount of time between these two locations
                        // so save the current segment and start a new segment.
                        locationHistorySegments.Add(currentSegment);
                        currentSegment = new List<WiELocationDataObject>();
                    }
                }

                // Add the current location to the curent segment
                currentSegment.Add(location);

                // Remember the location for next iteration
                prevLocation = location;
            }

            // Save the last segment if it has anything in it
            if (currentSegment.Count > 0)
                locationHistorySegments.Add(currentSegment);

            // Return the results.
            return (locationHistorySegments);
        }

        /// <summary>
        /// Helper method that buids an HTML description for a member-location pair, showing the member's name
        /// and key items about the member's current location.
        /// </summary>
        /// <param name="p_member"></param>
        /// <param name="p_location"></param>
        /// <returns></returns>
        string BuildMemberLocationDescription(WiEMemberDataObject p_member, WiELocationDataObject p_location)
        {
            string strDescription = "<table>";
            strDescription += ((p_member.FirstName != null) && (p_member.LastName != null)) ? "<tr><td><b>" + p_member.FirstName + " " + p_member.LastName + "</td></tr>" : "</td></tr>";
            strDescription += (p_member.PhoneNumber != null) ? "<tr><td><b>Phone Number: </b>" + p_member.PhoneNumber : "</td></tr>";
            strDescription += (p_member.Email != null) ? "<tr><td><b>E-mail: </b>" + p_member.Email : "</td></tr>";
            strDescription += (p_location.Speed != null) ? "<tr><td></td></tr><tr><td><b>Traveling at: </b>" + String.Format("{0:F}", p_location.Speed) + " mph." : "</td></tr>";
            if (p_location.Heading != null)
            {
                strDescription += "<tr><td><b>Heading: </b>";

                if ((p_location.Heading <= 15) || (p_location.Heading >= 345))
                    strDescription += "North";
                else if (p_location.Heading <= 75)
                    strDescription += "Northeast";
                else if (p_location.Heading <= 105)
                    strDescription += "East";
                else if (p_location.Heading <= 165)
                    strDescription += "Southeast";
                else if (p_location.Heading <= 195)
                    strDescription += "South";
                else if (p_location.Heading <= 255)
                    strDescription += "Southwest";
                else if (p_location.Heading <= 285)
                    strDescription += "Northwest";

                strDescription += "</td></tr>";
            }

            strDescription += "<tr><td></td></tr><tr><td><b>Position: </b>(" + ((double)p_location.Latitude).ToString("F3") + ", " + ((double)p_location.Longitude).ToString("F3") + ")</td></tr>";
            strDescription += "<tr><td></td></tr><tr><td><b>Time (UTC): </b>" + p_location.DateCollected.ToString() + "</td></tr>";
            strDescription += "</table>";

            return strDescription;
        }

        /// <summary>
        /// Helper method that returns an HTML formated table that describes the member and the time period for the location
        /// history (start / end time, distance, etc.)
        /// </summary>
        /// <param name="p_member"></param>
        /// <param name="p_locationHistory"></param>
        /// <returns></returns>
        string BuildMemberLocationHistoryDescription(WiEMemberDataObject p_member, List<WiELocationDataObject> p_locationHistory)
        {
            string strDescription = "<table>";
            strDescription += ((p_member.FirstName != null) && (p_member.LastName != null)) ? "<tr><td><b>" + p_member.FirstName + " " + p_member.LastName + "</td></tr>" : "</td></tr>";
            strDescription += (p_member.PhoneNumber != null) ? "<tr><td><b>Phone Number: </b>" + p_member.PhoneNumber : "</td></tr>";
            strDescription += (p_member.Email != null) ? "<tr><td><b>E-mail: </b>" + p_member.Email : "</td></tr>";

            // Try to calculate some interesting tidbits, like max speed, total distance, etc.
            double fMaxSpeed = 0;
            double fDistance = 0;
            DateTime dtStart = DateTime.MaxValue;
            DateTime dtStop = DateTime.MinValue;
            WiELocationDataObject prevLocation = null;
            foreach (WiELocationDataObject location in p_locationHistory)
            {
                // Keep track of the maximum speed we reached
                if (location.Speed != null)
                    if (location.Speed > fMaxSpeed)
                        fMaxSpeed = (double) location.Speed;

                if (prevLocation != null)
                {
                    // Keep track of the total distance between all the points.
                    fDistance += location.DistanceTo(prevLocation);

                    // Keep track of the start / stop time (technically we could cheat and grap first and last
                    // element since these lists are expected to be sorted.
                    dtStart = (dtStart > location.DateCollected) ? (DateTime)location.DateCollected : dtStart;
                    dtStop = (dtStop < location.DateCollected) ? (DateTime)location.DateCollected : dtStop;
                }
                else
                {
                    dtStart = (DateTime) location.DateCollected;
                    dtStop = (DateTime) location.DateCollected;
                }
                
                // Remember previous location for next iteration
                prevLocation = location;
            }

            // Show the peak speed during the segment
            strDescription += (fMaxSpeed > 0) ? "<tr><td></td></tr><tr><td><b>Max Speed: </b>" + String.Format("{0:F}", fMaxSpeed) + " mph." : "</td></tr>";

            // Show the total distance
            strDescription += (fDistance > 0) ? "<tr><td><b>Est. Distance: </b>" + String.Format("{0:F}",fDistance) + " meters." : "</td></tr>";

            // Show the start & stop time
            strDescription += "<tr><td></td></tr><tr><td><b>Start Time (UTC): </b>" + dtStart.ToString() + "</td></tr>";
            strDescription += "<tr><td></td></tr><tr><td><b>Stop Time (UTC): </b>" + dtStop.ToString() + "</td></tr>";

            strDescription += "</table>";

            return strDescription;
        }

        /// <summary>
        /// Helper method that populates the list of friends from query to the model.
        /// </summary>
        protected void PopulateFriendsList()
        {
            m_checkBoxListFriends.Items.Clear();

            // TODO: In the future this should not be the list of all members, rather the list of friends
            List<WiEMemberDataObject> members = m_model.GetMembers();

            foreach (WiEMemberDataObject member in members)
            {
                ListItem newItem = new ListItem();

                newItem.Text = member.FirstName + " " + member.LastName;
                newItem.Value = member.MemberID.ToString();

                m_checkBoxListFriends.Items.Add(newItem);
            }
        }

        protected void PopulatePointsOfInterestList()
        {

        }

        protected void PopulatePhotoList()
        {
        }

        [WebMethod]
        public static DateTime GetServerTime()
        {
            return DateTime.Now;
        }
    }
}
